package com.xuecheng.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.xuecheng.exception.XueChengPlusException;
import com.xuecheng.media.mapper.MediaProcessMapper;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.model.*;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.service.MediaFileService;
import com.xuecheng.model.PageParams;
import com.xuecheng.model.PageResult;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.activation.MimeType;
import java.io.*;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @description TODO
 * @author Mr.M
 * @date 2022/9/10 8:58
 * @version 1.0
 */
 @Service
 @Slf4j
public class MediaFileServiceImpl implements MediaFileService {

  @Autowired
 MediaFilesMapper mediaFilesMapper;
  @Autowired
    MinioClient minioClient;
  @Autowired
    MediaFileService currentProxy;
  @Autowired
    MediaProcessMapper mediaProcessMapper;

  @Value("${minio.bucket.files}")
  private String bucket_files;

  @Value("${minio.bucket.videofiles}")
  private String bucket_video;


 @Override

 public PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {

  //构建查询条件对象
  LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();
  
  //分页对象
  Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
  // 查询数据内容获得结果
  Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
  // 获取数据列表
  List<MediaFiles> list = pageResult.getRecords();
  // 获取数据总数
  long total = pageResult.getTotal();
  // 构建结果集
  PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
  return mediaListResult;

 }

 //获取文件默认存储目录路径 年/月/日
 private String getDefaultFolderPath(){
     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
     String format = sdf.format(new Date()).replace("-","/")+"/";
     return format;
 }
 //获取文件的mimType
 private String getMimeType(String extension) {
    if(extension==null){
        extension="";
    }
     ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
     String mimeType= MediaType.APPLICATION_OCTET_STREAM_VALUE;
     if(extensionMatch!=null){
         mimeType=extensionMatch.getMimeType();
     }
     return mimeType;
 }

 //获取文件md5值
 private String getFileMd5(File file){
     try (FileInputStream fileInputStream = new FileInputStream(file)) {
         String fileMd5 = DigestUtils.md5Hex(fileInputStream);
         return fileMd5;
     } catch (Exception e) {
         e.printStackTrace();
         return null;
     }
 }

 //上传到虚拟机的minio
 public boolean addMediaFilesToMinIO(String localFilePath, String mimeType, String bucketFiles, String objectName) {
    try {
        UploadObjectArgs build = UploadObjectArgs.builder()
                .bucket(bucketFiles)
                .object(objectName)
                .filename(localFilePath)
                .contentType(mimeType)
                .build();
        minioClient.uploadObject(build);
        log.debug("上传文件到minio成功,bucket:{},objectName:{}",bucketFiles,objectName);
        System.out.println("上传成功");
        return true;
    }catch (Exception e){
        XueChengPlusException.cast("上传文件到minio系统失败");
    }
    return false;
 }

    @Override
    public MediaFiles getFileById(String mediaId) {
        MediaFiles mediaFiles = mediaFilesMapper.selectById(mediaId);
        return mediaFiles;
    }

    @Transactional
 public MediaFiles addMediaFilesToDb(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucketFiles, String objectName) {
     MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);

     if(mediaFiles==null){
         mediaFiles = new MediaFiles();
         BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
         mediaFiles.setId(fileMd5);
         mediaFiles.setFileId(fileMd5);
         mediaFiles.setCompanyId(companyId);
         mediaFiles.setUrl("/"+bucketFiles+"/"+objectName);
         mediaFiles.setBucket(bucketFiles);
         mediaFiles.setFilePath(objectName);
         mediaFiles.setCreateDate(LocalDateTime.now());
         mediaFiles.setAuditStatus("002003");
         mediaFiles.setStatus("1");

         int insert = mediaFilesMapper.insert(mediaFiles);
         if(insert<=0){
             log.error("保存文件信息到数据库失败,{}",mediaFiles.toString());
             XueChengPlusException.cast("保存文件信息失败");
         }
         //添加到待处理任务表
         addWaitingTask(mediaFiles);

         log.debug("保存文件信息到数据库成功,{}",mediaFiles.toString());
     }
     return mediaFiles;
 }
    //添加到待处理任务表
    private void addWaitingTask(MediaFiles mediaFiles){
        String filename = mediaFiles.getFilename();

        String extension = filename.substring(filename.lastIndexOf("."));
        String mimeType = getMimeType(extension);
        if(mimeType.equals("video/x-msvideo")){
            MediaProcess mediaProcess = new MediaProcess();
            BeanUtils.copyProperties(mediaFiles,mediaProcess);
            mediaProcess.setFailCount(0);
            mediaProcess.setStatus("1");
            mediaProcessMapper.insert(mediaProcess);
        }
    }

    //检查文件是否存在
    @Override
    public RestResponse<Boolean> checkFile(String fileMd5) {
        //先查询数据库
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if(mediaFiles!=null){
            //如果数据库存在，再查询minio系统是否存在
            //桶
            String bucket = mediaFiles.getBucket();
            //存储目录
            String filePath = mediaFiles.getFilePath();
            //文件流
            InputStream inputStream=null;
            try {
                inputStream = minioClient.getObject(
                        GetObjectArgs.builder()
                                .bucket(bucket)
                                .object(filePath)
                                .build()
                );
                if(inputStream!=null){
                    //文件已存在
                    return RestResponse.success(true);
                }
            }catch (Exception e){

            }
        }
        //文件未存在
        return RestResponse.success(false);
    }

    //检查文件是否分块
    @Override
    public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
        //得到分块文件上传的目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath=chunkFileFolderPath+chunkIndex;
        //文件流
        InputStream inputStream=null;
        try {
            inputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket_video)
                            .object(chunkFilePath)
                            .build()
            );
            if(inputStream!=null){
                return RestResponse.success(true);
            }
        }catch (Exception e){

        }
        //分块未存在
        return RestResponse.success(false);
    }

    //上传分块
    @Override
    public RestResponse uploadChunk(String fileMd5, int chunk, String localChunkFilePath) {
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //分块文件的路径
        String chunkFilePath=chunkFileFolderPath+chunk;
        //mimetype
        String mimeType = getMimeType(null);
        boolean b = addMediaFilesToMinIO(localChunkFilePath, mimeType, bucket_video, chunkFilePath);
        if(!b){
            log.debug("上传分块文件失败:{}", chunkFilePath);
            return RestResponse.validfail(false,"分块上传失败");
        }
        log.debug("上传分块文件成功{}",chunkFilePath);


        return RestResponse.success(true);
    }

    //合并分块
    @Override
    public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
        //获取分块文件路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //将分块文件路径组成list
        List<ComposeSource> sourceList = Stream.iterate(0, i -> ++i).limit(chunkTotal)
                .map(i -> ComposeSource.builder().bucket(bucket_video).object(chunkFileFolderPath.concat(Integer.toString(i)))
                        .build())
                .collect(Collectors.toList());

        //合并
        //文件名称
        String filename = uploadFileParamsDto.getFilename();
        //文件扩展名
        String extension = filename.substring(filename.lastIndexOf("."));
        //合并文件路径
        String mergeFilePath = getFilePathByMd5(fileMd5, extension);
        try {
            //合并文件
            ObjectWriteResponse response = minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucket_video)
                            .object(mergeFilePath)
                            .sources(sourceList)
                            .build());
            log.debug("合并文件成功:{}",mergeFilePath);

        }catch (Exception e){
            log.error("合并文件失败:{}",mergeFilePath);
            return RestResponse.validfail(false,"合并文件失败");
        }

        //验证md5
        //下载合并后的文件
        File minioFile = downloadFileFromMinIO(bucket_video, mergeFilePath);
        if(minioFile==null){
            log.debug("从minio下载文件失败,mergeFilePath:{}",mergeFilePath);
            return RestResponse.validfail(false,"下载合并后文件失败");
        }

        try(FileInputStream fileInputStream = new FileInputStream(minioFile)){
            String md5Hex = DigestUtils.md5Hex(fileInputStream);
            if(!fileMd5.equals(md5Hex)){
                return RestResponse.validfail(false,"文件合并校验失败，最终上传失败");
            }
            //文件大小设置，后续文件信息入库需要用到
            uploadFileParamsDto.setFileSize(minioFile.length());

        }catch (Exception e){
            log.debug("文件合并校验失败，fileMd5:{}，异常:{}",fileMd5,e.getMessage(),e);
            return RestResponse.validfail(false,"文件合并校验失败");
        }

        //文件信息入库
        currentProxy.addMediaFilesToDb(companyId,fileMd5,uploadFileParamsDto,bucket_video,mergeFilePath);

        //清除分块文件
        clearChunkFiles(chunkFileFolderPath,chunkTotal);


        return RestResponse.success(true);
    }

    //清楚分块文件
    private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){
     try {
         List<DeleteObject> objects = Stream.iterate(0, i -> ++i).limit(chunkTotal)
                 .map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
                 .collect(Collectors.toList());
         RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket("video").objects(objects).build();
         Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
         results.forEach(r->{
             DeleteError deleteError=null;
             try {
                 deleteError=r.get();
             }catch (Exception e){
                 e.printStackTrace();
                 log.error("清除分块文件失败：{}",deleteError,e.getMessage(),e);
             }
         });
     }catch (Exception e){
        e.printStackTrace();
        log.error("清除分块文件失败：{}",chunkFileFolderPath,e);
     }
    }

    //从minio下载文件
    public File downloadFileFromMinIO(String bucket,String objectName){
        //临时文件
        File minioFile = null;
        FileOutputStream outputStream = null;
        try{
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .build());
            //创建临时文件
            minioFile=File.createTempFile("minio", ".merge");
            outputStream = new FileOutputStream(minioFile);
            IOUtils.copy(stream,outputStream);
            return minioFile;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    //得到合并文件后的地址
    private String getFilePathByMd5(String fileMd5,String fileExt){
        return   fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
    }

    //得到分块文件的目录
    private String getChunkFileFolderPath(String fileMd5) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
    }


    //上传文件实现方法入口
 @Override
 public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath,String objectName) {
     File file = new File(localFilePath);
     if(!file.exists()){
         XueChengPlusException.cast("文件不存在");
     }

     String filename = uploadFileParamsDto.getFilename();
     //文件扩展名
     String extension = filename.substring(filename.lastIndexOf("."));
     //文件mimetype
     String mimeType=getMimeType(extension);
     //文件md5值
     String fileMd5=getFileMd5(file);
     //文件默认目录
     String defaultFolderPath = getDefaultFolderPath();
     //存储到minio中的对象名(带目录)
     if(StringUtils.isEmpty(objectName)) {
         objectName = defaultFolderPath + fileMd5 + extension;
     }
     //上传到Minio
     boolean b=addMediaFilesToMinIO(localFilePath, mimeType, bucket_files, objectName);

     uploadFileParamsDto.setFileSize(file.length());
     //将文件信息存储到数据库
     MediaFiles mediaFiles=currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);

     UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
     BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
     return uploadFileResultDto;
 }

}
